#if !defined(__MEMCACHEASIO_CONNECTION_H__)
#define __MEMCACHEASIO_CONNECTION_H__

/* Copyright (c) 2008, dZettaApart.
   Licensed under the BSD License.
   See COPYING for details. */

#include <cstdlib>
#include <iostream>

#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/tuple/tuple.hpp>

#include "memcacheasio_request.h"

namespace memcacheasio
{
	class Connection : public boost::enable_shared_from_this<Connection>,
				private boost::noncopyable
	{
		public:
			typedef boost::shared_ptr<Connection> Ptr;
			typedef boost::function2<void, const boost::system::error_code&, Connection::Ptr > cb_connection;
			typedef boost::function2<void, const boost::system::error_code&, Request::Ptr > cb_process_request;

			Connection ( boost::asio::io_service& io_service ) :
					_io_service ( io_service ),
					_resolver ( io_service ),
					_socket ( io_service ) {};

			///Async connect to server, resolve address
			template <typename Handler>
			void connect ( std::string host, std::string port, Handler handler )
			{
				void ( Connection::*f ) ( const boost::system::error_code&,
				                          boost::asio::ip::tcp::resolver::iterator ,
				                          boost::tuple<Handler> )
				= &Connection::handle_resolve<Handler>;

				std::cout << "Create connection to host: " << host << ":" << port << std::endl;
				// Start an asynchronous resolve to translate the server and service names
				// into a list of endpoints.
				boost::asio::ip::tcp::resolver::query query ( host, port );
				_resolver.async_resolve ( query,
				                          boost::bind ( f, shared_from_this(),
				                                        boost::asio::placeholders::error,
				                                        boost::asio::placeholders::iterator,
				                                        boost::make_tuple ( handler ) ) );
			}

			/// Make request to server
			void processRequest ( Request::Ptr request, cb_process_request cb, cb_connection cb2 )
			{
				try
				{
					_request = request->request();
					boost::asio::async_write ( _socket,
					                           boost::asio::buffer ( _request.data(),    _request.length() ),
					                           boost::bind ( &Connection::handle_write, shared_from_this(), request, cb, cb2,
					                                         boost::asio::placeholders::error ) );
				}
				catch ( const boost::system::system_error& e )
				{
					cb2 ( e.code(), shared_from_this() );
					cb ( e.code(), request );
				}
			};

			/// Close socket
			void close()
			{
				_socket.close();
			};

			/// Determine whether the socket is open.
			bool is_open() const
			{
				return _socket.is_open();
			}

		private:
			static const std::string expression;
			static const std::string expression_incdec;

			boost::asio::io_service&	_io_service;
			boost::asio::ip::tcp::socket 	_socket;
			boost::asio::ip::tcp::resolver  _resolver;

			boost::asio::streambuf 	_response;
			std::string 		   _request;

			template <typename Handler>
			void handle_resolve ( const boost::system::error_code& err,
			                      boost::asio::ip::tcp::resolver::iterator endpoint_iterator,
			                      boost::tuple<Handler> handler )
			{
				void ( Connection::*f ) ( const boost::system::error_code&,
				                          boost::asio::ip::tcp::resolver::iterator ,
				                          boost::tuple<Handler> )
				= &Connection::handle_connect<Handler>;

				if ( !err )
				{
					// Attempt a connection to the first endpoint in the list. Each endpoint
					// will be tried until we successfully establish a connection.
					boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
					_socket.async_connect ( endpoint,
					                        boost::bind ( f, shared_from_this(),
					                                      boost::asio::placeholders::error, ++endpoint_iterator, handler ) );
				}
				else
				{
					boost::get<0> ( handler ) ( err );
				}
			}

			template <typename Handler>
			void handle_connect ( const boost::system::error_code& err,
			                      boost::asio::ip::tcp::resolver::iterator endpoint_iterator,
			                      boost::tuple<Handler> handler )
			{
				void ( Connection::*f ) ( const boost::system::error_code&,
				                          boost::asio::ip::tcp::resolver::iterator ,
				                          boost::tuple<Handler> )
				= &Connection::handle_connect<Handler>;

				if ( !err )
				{
					// The connection was successful.
					boost::get<0> ( handler ) ( err );
				}
				else if ( endpoint_iterator != boost::asio::ip::tcp::resolver::iterator() )
				{
					// The connection failed. Try the next endpoint in the list.
					_socket.close();
					boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
					_socket.async_connect ( endpoint,
					                        boost::bind ( f, shared_from_this(),
					                                      boost::asio::placeholders::error, ++endpoint_iterator, handler ) );
				}
				else
				{
					boost::get<0> ( handler ) ( err );
				}
			}

			void handle_write ( Request::Ptr request, cb_process_request cb, cb_connection cb2, const boost::system::error_code& err )
			{
				if ( !err )
				{
					try
					{
						switch ( request->command() )
						{
							case Request::COMMAND_INCR:
							case Request::COMMAND_DECR:
								boost::asio::async_read_until ( _socket, _response,
								                                boost::regex ( expression_incdec ),
								                                boost::bind ( &Connection::handle_read, shared_from_this(), request, cb, cb2,
								                                              boost::asio::placeholders::error ) );
								break;
							default:
								boost::asio::async_read_until ( _socket, _response,
								                                boost::regex ( expression ),
								                                boost::bind ( &Connection::handle_read, shared_from_this(), request, cb, cb2,
								                                              boost::asio::placeholders::error ) );
								break;
						}
						return;
					}
					catch ( boost::system::system_error& e )
					{
					}
				}
				else
				{
				}

				cb2 ( err, shared_from_this() );
				cb ( err, request );
			}

			void handle_read ( Request::Ptr request, cb_process_request cb, cb_connection cb2, const boost::system::error_code& err )
			{
				if ( !err )
				{
					// Process the response
					std::istream response ( &_response );
					//std::cout << "Procesing ticket: got response \n\n";
					std::ostringstream data;
					std::string line;
					while ( getline ( response, line ) )
					{
						data << line << '\n';
					};
					std::string data_string ( data.str() );

					request->setResponse ( data_string );
				}
				else
				{
				}

				cb2 ( err, shared_from_this() );
				cb ( err, request );
			}
	};

}; //namespace memcacheasio

#endif // !defined(__MEMCACHEASIO_CONNECTION_H__)

